﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Web;
using CncConvProg.Geometry;
using CncConvProg.Geometry.Entity;
using CncConvProg.Geometry.PreviewPathEntity;
using CncConvProg.Model.ConversationalStructure;
using CncConvProg.Model.ConversationalStructure.Abstraction;
using CncConvProg.Model.ConversationalStructure.Lavorazioni.Common;
using CncConvProg.Model.ConversationalStructure.Lavorazioni.Foratura;
using CncConvProg.Model.PathGenerator;
using CncConvProg.Model.PreviewPathEntity;

namespace CncConvProg.Model.ToolMachine
{

    /*
     * fare statistiche lavorazione
     * 
     * totali e in base al tool..
     * . tempo taglio
     *  tempo rapido
     *  tempo sosta
     *  tempo cambio tool
     *  lunghezza percorso
     *  luunghezza lavoro
     *  lunghezza rapido.
     *  feed max
     *  feed min
     *  
     *  bounfing box per la lavorazione
     *  numero di tool
     *  
     * lista di utensile e bounding box lavorazione..
     */

    [Serializable]
    public abstract class ToolMachine
    {
        #region Time Calculation Stuff
        /*
         * Inserisco campi per tenere memorizzare tempi di default.
         * Ogni fase potra poi personalizzare il tempo.
         */

        /// <summary>
        /// Tempo medio programmazione operazione 
        /// Espresso in minuti
        /// </summary>
        public int AverageProgrammingOperationTime { get; set; }

        /// <summary>
        /// Tempo medio preparazione staffaggio.
        /// Espresso in minuti
        /// </summary>
        public int AverageSetupFixtureTime { get; set; }

        /// <summary>
        /// Tempo Medio Montaggio Utensile
        /// Espresso in minuti
        /// </summary>
        public int AverageMountingToolTime { get; set; }

        /// <summary>
        /// Tempo Default Caricamento Pezzo
        /// Espresso in secondi
        /// </summary>
        public int MachineLoadingTime { get; set; }

        /// <summary>
        /// Costo Orario Macchina
        /// </summary>
        public double CostoOrario { get; set; }

        /// <summary>
        /// Rapidi
        /// </summary>
        public double VelocitaRapido { get; set; }
        public double MaxGiri { get; set; }


        #endregion

        protected ToolMachine()
        {
            MachineGuid = Guid.NewGuid();
            AverageProgrammingOperationTime = 6;
            AverageSetupFixtureTime = 20;
            AverageMountingToolTime = 5;
            MachineLoadingTime = 30;
            ChangeToolTime = 6;
            CostoOrario = 50;
            VelocitaRapido = 15000;
            MaxGiri = 6000;
        }
        public string MachineName { get; set; }
        public Guid MachineGuid { get; private set; }

        public abstract FaseDiLavoro CreateFaseLavoro();


        protected void SetDefaultTiming(FaseDiLavoro faseDiLavoro)
        {
            faseDiLavoro.AverageProgrammingOperationTime = AverageProgrammingOperationTime;
            faseDiLavoro.AverageSetupFixtureTime = AverageSetupFixtureTime;
            faseDiLavoro.AverageMountingToolTime = AverageMountingToolTime;
            faseDiLavoro.MachineLoadingTime = MachineLoadingTime;
        }


        protected MoveType CurrentMoveType = MoveType.Rapid;

        // todo _ inizializzarli con valori adeguati a macchina

        // obs 
        protected double? CurrentX = null;
        protected double? CurrentY = null;
        protected double? CurrentZ = null;

        protected MeasureUnit MeausureUnit { get; private set; }

        //// 
        //private Point3D _currentPos;

        //protected Point3D GetCurrentPos()
        //{
        //    if (_currentPos != null)
        //        return _currentPos;

        //    return null;
        //}



        protected WorkPlane CurrentWorkPlane { get; set; }

        /// <summary>
        /// forse questo metodo non è necessario che risieda qui..
        /// </summary>
        /// <param name="programPhase"></param>
        /// <returns></returns>
        public List<IPreviewEntity> GetPreview(ProgramPhase programPhase)
        {
            if (programPhase == null)
                return null;

            var path3D = new PreviewPathBuilder();

            // path3D.SetStartPoint(CurrentX, CurrentY, CurrentZ);

            foreach (var programAction in programPhase.Actions)
            {

                // qui fare metodo ricorsivo..
                if (programAction is MacroDrillingAction)
                {
                    var macro = programAction as MacroDrillingAction;
                    //CreatePreview(programAction as MacroDrillingAction, path3D);
                    foreach (var v in macro.MoveActionCollection)
                    {
                        if (v is ArcMoveAction)
                        {
                            var moveAction = programAction as ArcMoveAction;

                            AddElement(path3D, moveAction);
                        }

                        else
                        {
                            AddElement(path3D, v);
                        }

                    }
                }

                else if (programAction is ArcMoveAction)
                {
                    var moveAction = programAction as ArcMoveAction;

                    AddElement(path3D, moveAction);


                }

                else if (programAction is LinearMoveAction)
                {
                    var moveAction = programAction as LinearMoveAction;

                    AddElement(path3D, moveAction);

                }

            }

            return path3D.GetProfile();
        }

        private static void AddElement(PreviewPathBuilder path3D, LinearMoveAction moveAction)
        {
            var plotStyle = EnumPlotStyle.Path;

            if (moveAction.MoveType == MoveType.Rapid)
                plotStyle = EnumPlotStyle.RapidMove;

            path3D.AddLine(plotStyle, moveAction.X, moveAction.Y, moveAction.Z, moveAction.ParametroVelocita);

        }

        private static void AddElement(PreviewPathBuilder path3D, ArcMoveAction moveAction)
        {
            var center = new Point3D(moveAction.Center.X, moveAction.Center.Y, 0);

            path3D.AddArc(EnumPlotStyle.Arc, center, moveAction.Radius, moveAction.ClockWise,
                moveAction.X, moveAction.Y, moveAction.Z, moveAction.ParametroVelocita);
        }


        protected double CurrentFeed { get; set; }

        //protected void SetFeed(double feed, ref StringBuilder code)
        //{
        //    if (CurrentMoveType != MoveType.Rapid)
        //        if (CurrentFeed != feed)
        //        {
        //            CurrentFeed = feed;

        //            code.Append("F" + FormatFeed(CurrentFeed));
        //        }
        //}

        public enum WorkPlane
        {
            XY,
            XZ,
            YZ,
            LatheXz,
        }



        /// <summary>
        /// In fase di creazione programma , o quando è necessario calcolare tempo macchina , bisogna tenere lo stato della macchina 
        /// ( posizione, modalita avanzamento e mod. velocita , valore avanzamanto ecc. .) .
        /// 
        /// Bisogna però resettare questo stato quando si ricalcola..
        /// </summary>
        private void ResetMachineStatus()
        {
            CurrentX = null;
            CurrentY = null;
            CurrentZ = null;
        }

        /// <summary>
        /// Da insieme di linee e archi restituisco 
        /// </summary>
        /// <param name="pathPreview"></param>
        /// <param name="limit"></param>
        /// <returns></returns>
        internal OperazioneTime GetTime(IList<IPreviewEntity> pathPreview, double limit)
        {
            /*
             * todo . dividere fra i tempi di rapido, lavoro , varie.
             */
            var rapid = new TimeSpan();
            var work = new TimeSpan();
            var workDistancePercurred = 0d;
            var rapidDistancePercurred = 0d;

            var mu = Singleton.Instance.MeasureUnit;

            /*
             * 
             */

            foreach (var previewable in pathPreview)
            {
                if (previewable.IsRapidMovement)
                {
                    /*
                     * oltre che lunghezza mi serve sapere inizio d e tipo mm o inch..
                     */
                    // ho inserisco qui il rapid.
                    double moveLenght;

                    var ml = CalcTime(previewable, out moveLenght, mu, limit);

                    rapid += ml;
                    rapidDistancePercurred += moveLenght;
                    //rapid += previewable.GetTimeSpan();
                    //rapidDistancePercurred += previewable.GetMoveLength;
                }
                else
                {
                    double moveLenght;

                    var ml = CalcTime(previewable, out moveLenght, mu, limit);

                    work += ml;
                    workDistancePercurred += moveLenght;
                    //work += previewable.GetTimeSpan();
                    //workDistancePercurred += previewable.GetMoveLength;

                }

            }

            var op = new OperazioneTime
                         {
                             TempoRapido = rapid,
                             TempoLavoro = work,
                             DistanzaPercorsaLavoro = workDistancePercurred,
                             DistanzaPercorsaRapido = rapidDistancePercurred,
                         };

            return op;
        }

        private static TimeSpan CalcTime(IPreviewEntity entity, out double moveLength, MeasureUnit measureUnit, double limitGiri)
        {
            try
            {
                moveLength = 0;

                moveLength = entity.GetMoveLength();

                double min = 0;

                if (moveLength <= 0) return new TimeSpan();

                if (entity.ParametroVelocita == null) return new TimeSpan();

                var feed = entity.ParametroVelocita.ValoreFeed;

                var modoV = entity.ParametroVelocita.ModoVelocita;

                var speed = entity.ParametroVelocita.ValoreVelocita;

                if (feed == 0) return new TimeSpan();

                if (entity.ParametroVelocita.ModoAvanzamento == VelocitaType.ASync)
                {
                    min = moveLength / feed;

                }
                else if (entity.ParametroVelocita.ModoAvanzamento == VelocitaType.Sync)
                {
                    if (entity is PreviewLine3D)
                    {
                        var l = entity as PreviewLine3D;

                        if (modoV == VelocitaType.Sync)
                        {
                            /*
                             * todo : considerare anche linee inclinate
                             */
                            if (l.IsHorizzontal())
                            {
                                min = TimeHelper.CalcHorizontalTime(l, speed, feed, measureUnit, limitGiri);
                            }
                            else
                                min = TimeHelper.CalcAcceleratedVerticalMove(l.Start.Y, l.End.Y,
                                                                             l.ParametroVelocita.ValoreVelocita, feed,
                                                                             limitGiri, measureUnit);


                        }
                        else
                        {
                            var velocitaSpazioMin = speed * feed;
                            if (velocitaSpazioMin == 0) return new TimeSpan();

                            min = moveLength / velocitaSpazioMin;
                        }

                    }
                    else if (entity is Arc3D)
                    {

                        if (modoV == VelocitaType.Sync)
                        {
                            var arc = entity as Arc3D;
                            /*
                             * todo : fare simile per linea , per ora visto che comunque è un caso raro 
                             * non tengo conto dell'accelerazione per arco.
                             * come diametro considero diametro maggiore
                             */
                            var d = Math.Max(arc.Start.Y, arc.End.Y);

                            var nGiri = FeedAndSpeedHelper.GetNumeroGiri(speed, d, measureUnit);

                            var vel = nGiri * feed;
                            if (vel == 0) return new TimeSpan();
                            min = moveLength / vel;
                        }
                        else
                        {
                            var vel = speed * feed;

                            if (vel == 0) return new TimeSpan();
                            min = moveLength / vel;
                        }
                    }
                }

                if (double.IsNaN(min))
                    min = 0;

                return TimeSpan.FromMinutes(min);
            }
            catch (Exception)
            {

                throw;
            }




        }


        public int ChangeToolTime { get; set; }
    }
}



